package org.bdware.irp.irplib.core;

import com.google.gson.*;
import org.apache.log4j.Logger;
import org.bdware.irp.irplib.exception.IrpMessageDecodeException;
import org.bdware.irp.irplib.util.EncoderUtils;
import org.bdware.irp.irplib.util.GlobalUtils;

import java.lang.reflect.Type;
import java.text.SimpleDateFormat;
import java.util.Date;

public class HandleResponse extends IrpResponse {
    private static Logger logger = Logger.getLogger(EncoderUtils.class);

    public byte[] handle;
    public byte[][] values;

    /**
     * Creates an IrpResponse.
     */
    public HandleResponse(int opCode, int resCode) {
        super(opCode, resCode);
        this.requestDigest = null;
    }
    public static HandleResponse newResolveHandleResponse(byte[] handle, byte[][] values){
        HandleResponse res = new HandleResponse(IrpMessageCode.OC_RESOLUTION, IrpMessageCode.RC_SUCCESS);
        res.handle = handle;
        res.requestDigestNeeded = false;
        res.values = values;
        return res;
    }

    public String getHandle() {
        return GlobalUtils.decodeString(this.handle);
    }

    public HandleValue[] getHandleValues() {
        try{
            HandleValue[] retValues = new HandleValue[values.length];
            for (int i = 0; i < retValues.length; i++) {
                retValues[i] = decodeHandleValue(values[i]);
            }
            return retValues;
        }catch(Exception e){
            e.printStackTrace();
            logger.error("encode the handle values error");
            return null;
        }
    }

    public String getHandleValuesAsJson(){
        GsonBuilder gsonBuilder = new GsonBuilder();
        gsonBuilder.registerTypeAdapter(HandleValue.class, new HandleValueAdapter());
        gsonBuilder.registerTypeAdapter(ValueReference.class, new ValueReferenceAdapter());
        return gsonBuilder.create().toJson(getHandleValues());
    }

    public HandleValue decodeHandleValue(byte[] buf) throws IrpMessageDecodeException {
        int offsetPos = 0;
        HandleValue value = new HandleValue();
        // index 4 bytes
        value.index = EncoderUtils.readIntFromBuffer(buf, offsetPos);
        offsetPos += EncoderUtils.INT_SIZE;
        // timestamp 4 bytes
        value.timestamp = EncoderUtils.readIntFromBuffer(buf, offsetPos);
        offsetPos += EncoderUtils.INT_SIZE;
        // ttl Type 1 byte
        value.ttlType = buf[offsetPos++];
        // ttl 4 bytes
        value.ttl = EncoderUtils.readIntFromBuffer(buf, offsetPos);
        offsetPos += EncoderUtils.INT_SIZE;
        // permissions 1 byte
        value.setPermissions(buf[offsetPos++]);
        // type
        value.type = EncoderUtils.readStringByteFromBuffer(buf, offsetPos);
        offsetPos += EncoderUtils.INT_SIZE + value.type.length;
        // data
        value.data = EncoderUtils.readStringByteFromBuffer(buf, offsetPos);
        offsetPos += EncoderUtils.INT_SIZE + value.data.length;
        // references
        int referLength = EncoderUtils.readIntFromBuffer(buf, offsetPos);
        offsetPos += EncoderUtils.INT_SIZE;
        value.references = new ValueReference[referLength];
        for (int i = 0; i < referLength; i++) {
            value.references[i] = new ValueReference();
            value.references[i].handle = EncoderUtils.readStringByteFromBuffer(buf, offsetPos);
            offsetPos += EncoderUtils.INT_SIZE + value.references[i].handle.length;
            value.references[i].index =EncoderUtils.readIntFromBuffer(buf, offsetPos);
            offsetPos += EncoderUtils.INT_SIZE;
        }
        return value;
    }

    class HandleValue{
        public static final byte TTL_TYPE_RELATIVE = 0;
        public static final byte TTL_TYPE_ABSOLUTE = 1;

        int index = -1;
        byte[] type = null;
        byte[] data = null;
        byte ttlType = TTL_TYPE_RELATIVE;
        int ttl = 86400;
        int timestamp = 0;
        byte permissions = 0;
        ValueReference[] references = null;
        boolean adminRead = true;
        boolean adminWrite = true;
        boolean publicRead = true;
        boolean publicWrite = false;

        public HandleValue() {}

        public void setPermissions(byte permissions) {
            this.permissions = permissions;
            this.adminRead = (permissions & 0x8) != 0;
            this.adminWrite = (permissions & 0x4) != 0;
            this.publicRead = (permissions & 0x2) != 0;
            this.publicWrite = (permissions & 0x1) != 0;
        }

        public final String getPermissionString() {
            return new String(new char[] { adminRead ? 'r' : '-', adminWrite ? 'w' : '-', publicRead ? 'r' : '-', publicWrite ? 'w' : '-' });
        }

        public final JsonElement getTTLAsString() {
            if (ttlType == HandleValue.TTL_TYPE_RELATIVE) {
                return new JsonPrimitive(Integer.valueOf(ttl));
            } else {
                SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
                return new JsonPrimitive(simpleDateFormat.format(new Date(ttl*1000)));
            }
        }
        public final JsonElement getPermissions(){
            return new JsonPrimitive(bit(adminRead) + bit(adminWrite) + bit(publicRead) + bit(publicWrite));
        }

        private  String bit(boolean b) {
            return b ? "1" : "0";
        }

        @Override
        public String toString(){
            return " index=" + index + " type=" + (type == null ? "" : GlobalUtils.decodeString(type)) + " " + getPermissionString() + " \""
                    + (data == null ? "" : GlobalUtils.decodeString(data)) + '"';
        }
    }
    class HandleValueAdapter implements JsonSerializer<HandleValue> {

        @Override
        public JsonElement serialize(HandleValue src, Type typeOfSrc, JsonSerializationContext context) {
            JsonObject json = new JsonObject();
            json.addProperty("index", src.index);
            json.addProperty("type", GlobalUtils.decodeString(src.type));
            //to do, the data need to decode by algorithm, not decode to string 
            json.addProperty("data", GlobalUtils.decodeString(src.data));
            json.add("permissions", src.getPermissions());
            json.add("ttl", src.getTTLAsString());
            json.addProperty("timestamp", Integer.valueOf(src.timestamp));
            if (src.references != null && src.references.length > 0) {
                json.add("references", context.serialize(src.references));
            }
            return json;
        }

    }
    class ValueReference {
        public byte[] handle;
        public int index;

        public ValueReference(){ }
        public ValueReference(byte[] handle, int index) {
            this.handle = handle;
            this.index = index;
        }

        public ValueReference(String handleString, int index) {
            this.handle = GlobalUtils.encodeString(handleString);
            this.index = index;
        }
        @Override
        public String toString(){
            return GlobalUtils.decodeString(handle) + ":" + index;
        }
    }

    public class ValueReferenceAdapter implements JsonSerializer<ValueReference>, JsonDeserializer<ValueReference> {
        @Override
        public JsonElement serialize(ValueReference src, Type typeOfSrc, JsonSerializationContext context) {
            JsonObject json = new JsonObject();
            json.addProperty("handle", GlobalUtils.decodeString(src.handle));
            json.addProperty("index", Integer.valueOf(src.index));
            return json;
        }

        @Override
        public ValueReference deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
            try {
                JsonObject obj = json.getAsJsonObject();
                String handle = obj.get("handle").getAsString();
                int index = obj.get("index").getAsInt();
                return new ValueReference(handle, index);
            } catch (JsonParseException e) {
                throw e;
            } catch (Exception e) {
                throw new JsonParseException(e);
            }
        }
    }
}
